In [42]:
import matplotlib.pyplot as plt
import numpy as np
%matplotlib inline

What are classes?

  • A way of organising your code
  • Data is inherently linked to the things you can do with it. ## Pros
  • Can do everything you can do without classes, but idea is to make it easier
  • Classes encourage code reuse through a concept called "inheritance" - we will discuss later. ## Cons
  • Can make your code more complicated, and without careful thinking, harder to maintain.
  • More work for the developer.

Start by defining some terminology - Classes vs Objects vs Instances

  • Often used interchangably but they are different concepts.
  • A Class is like a template - you could consider the class "Car"
  • An object is a particular occurence of a class - so, for example, you could have "Ford Mondeo", "Vauxhall Astra", "Lamborghini Gallardo" be objects of type "Car".
  • An instance is a unique single object.

Where are classes used in Python? Everywhere!

You've been using classes all of the time, without even knowing it. Everything in Python is an object. You have some data (number, text, etc.)with some methods (or functions) which are internal to the object, and which you can use on that data. Lets look at a few examples...


In [ ]:

How can I see what methods an object of type float has?


In [ ]:


In [ ]:

Aside - What do all those underscores mean?

They're hidden methods - we'll talk more about these later.

Creating a class

Define some key things:

  • self - 'self' is a special type of variable which can be used inside the class to refer to itself.
  • Methods - a function which is part of a class, and which have access to data held by a class.
  • A constructor - a special method which is called when you create an instance of a class. In Python this function must be called "__init__"
  • A destructor - a special method which is called when you destroy an instance of a class.

Aside: If you're a C++/Java programmer, 'self' is exactly equivalent to 'this', but functions must have self as an argument, as it is passed in implicitly as the first argument of any method call in Python.


In [ ]:

Using the class

Use the same syntax as we use to call a function, BUT the arguments get passed in to the "__init__" function. Note that you ignore the self object, as Python sorts this out.


In [ ]:

How do I access data stored in the class? with the ".", followed by the name.


In [ ]:

What happens though if we reuse the variable name 'a'?

Aside:

  • Your computer has Random Access Memory (RAM) which is used to store information.
  • Whenever, in a programming language, you tell the language to store something, you effectively create a 'box' of memory to put those values in.
  • The location of the specific 'box' is known as a 'memory address'
  • You can see the memory address of a Python object quite easily:

In [ ]:

So, what happens if we either choose to store something else under the name 'a', or tell Python to delte it?


In [ ]:


In [ ]:

Why bother? This can be achieved without classes very easily:


In [ ]:

Need a better example!

How about a Simulation class?

  • Write once, but can take different parameters.
  • Can include data analysis methods as well

Consider a 1-D box of some length:

What information does it need to know about itself?

  • How big is the box?

In [ ]:

What we're going to try and do is add particles to the box, which have some properties:

  • An initial position.
  • An initial velocity

    $r(t + \delta t) \approx r(t) + v(t)\delta t$


In [ ]:

Lets just check this, if a Particle is in a box of length 10, has r0 = 0, v0=5, then after 1 step of length 3, the position should be at position 5:


In [ ]:

Lets add a place to store the particles to the box class, and add a method to add particles to the box:


In [ ]:

Now lets get you to do something...

Tasks (30-40 minutes):

1) Add a method that calculates the average position of Particles in the box (Hint: you might have to think about what to do when there are no particles!)

2) Add a method that makes all of the particles step forwards, and keep track of how much time has passed in the box class.

3) Add a method which plots the current position of the particles in the box.

4) Write a method that writes the current positions and velocities to a CSV file.

5) Write a method that can load a CSV file of positions and velocities, create particles with these and then add them to the Box list of particles. (Hint: Look up the documentation for the module 'csv')


In [ ]:


In [ ]:


In [ ]:


In [ ]:

Class Properties

  • Properties can be used to do interesting things
  • Special functions as part of a class that we mark with a 'decorator' - '@property'
  • Lets adjust the class Particle we used to make its data members a property of the class. We also need to write a 'setter' method to set the data members.

In [ ]:

Why bother? It looks the same when we use it!

  • Well known in programming - 'an interface is a contract'
  • You might want to at some point rewrite a large portion of the underlying data - how it is stored for example.
  • If you do this without using properties to access the data, you then need to go through all code that uses this class and change it to use the new variable names.

Inheritance

  • Last part of the course on Classes, but also one of the main reason for using classes!
  • Inheritance allows you to reuse parts of the code, but change some of the methods. Lets see how it might be useful...

In [ ]:

  • Here we have inherited most of the class Particle, and just changed the method 'step' to do something differently. Because we kept the properties the same, we can use this class everywhere that we could use Particle - our Box class can take a mixture of Particles and SlowParticles

Magic Methods:

Remember earlier, when we did:


In [ ]:

Notice that there is a method "__add__" - we can define these special methods to allow our class to do things that you can ordinarily do with built in types.


In [ ]:

Now we've created an 'add' method, we can, create two boxes and add these together!


In [ ]:

Looks good! But hang on...


In [ ]:


In [ ]:


In [ ]:

Why has the mean position of particles in Box C changed? Look at the memory address of the particles:


In [ ]:

Boxes are pointing to the SAME particles! If we don't want this to happen, we need to write a 'copy' constructor for the class - a function which knows how to create an identical copy of the particle!

We can do this by using the 'deepcopy' function in the 'copy' module, and redefine the particle and slow particle classes:


In [ ]:

Then, we should change the Box class's 'add' method, to use this copy operation rather than just append the child particles of the existing boxes:


In [ ]:


In [ ]:


In [ ]:


In [ ]: